/* License Notice:
**
** This program is free software: you can redistribute it and/or modify
**    it under the terms of the GNU General Public License as published by
**    the Free Software Foundation, either version 3 of the License, or
**    (at your option) any later version.
** This program is distributed in the hope that it will be useful,
**   but WITHOUT ANY WARRANTY; without even the implied warranty of
**   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
**   GNU General Public License for more details.
** You should have received a copy of the GNU General Public License
**   along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

/**
 * @file level.cpp
 * @author TooOld2Rock'nRoll
 * @see level.hpp
 */
/*---- Includes ----*/
#include "level.hpp"

#include "toolbox/debug.h"


/*---- Global Variables ----*/


/*---- Methods Implementation ----*/
/**
 * @brief Level Base Constructor.
 *
 * @param lvl_width - the level width in pixels.
 * @param lvl_height - the level height in pixels.
 */
Level::Level (unsigned lvl_width, unsigned lvl_height) :
        ui_lvl_width(lvl_width), ui_lvl_height(lvl_height)
{
    int i = 0;

    _debug (TRACE, "::%s (lvl_width=%u, lvl_height=%u)", __FUNCTION__, lvl_width, lvl_height);

    this->p_res_mgr_instance = &ResourceManager::instance ();
    this->_res_domain_id = this->p_res_mgr_instance->createNewDomain ();

    this->p_ev_mgr_instance = &EventManager::instance();
    this->id_normal_ev_listener = this->p_ev_mgr_instance->addListener ();

    this->p_audio_instance = &Audio::instance ();

    for (i = PLAYERS_COUNT; i--; )
    {
        this->ap_characters[i] = nullptr;
        this->v3pa_character_position[i] = nullptr;
    }//end for (i = PLAYERS_COUNT; i--; )
}//End Constructor

/**
 * @brief Level Destructor.
 */
Level::~Level ()
{
    _debug (TRACE, "::%s ()", __FUNCTION__);

    if (this->p_loading_thread)
        delete this->p_loading_thread; //this should kill the thread if active....
                                       //not good, program won't exit gracefully, but we won't lock the program either!

    this->p_ev_mgr_instance->removeListener (this->id_normal_ev_listener);
    this->p_res_mgr_instance->dropDomain (this->_res_domain_id);
}//End Destructor


/**
 * @brief Adds a playable character to the level's list of objects to control.
 * @remark All characters should be added BEFORE calling the load() method.
 *
 * @param player_index - the player number in the game.
 * @param pl_char - reference to the Character to be part of the Level.
 *
 * @throws std::out_of_range - if the player number is greater than the max number of players supported.
 * @throws std::runtime_error - if the GameObject does not represent a character.
 */
void Level::addPlayableCharacter (unsigned player_index, Character *pl_char)
{
    _debug (TRACE, "::%s ()", __FUNCTION__);

    if (player_index >= PLAYERS_COUNT)
        throw std::out_of_range (fmt::format ("{0}:{1} - Player out of range, {2} of {3}!", __LINE__, __FILE__, player_index, PLAYERS_COUNT));

    if (pl_char && pl_char->getKind () != Character::CHARACTER)
        throw std::runtime_error (fmt::format ("{0}:{1} - GameObject is not a Character!", __LINE__, __FILE__));
#ifdef ENABLE_DEBUG
    if (!pl_char)
        _debug (WARNING, "Setting a null character to the level, are you sure?");
    if (this->ap_characters[player_index])
        _debug (WARNING, "Player already set, are you sure you want to override it?");
#endif

    this->ap_characters[player_index] = pl_char;
    if (pl_char)
        this->v3pa_character_position[player_index] = &this->ap_characters[player_index]->getPosition().get ();
}//End addGameObject ()


/**
 * @brief Set the sprite position relative to a Level positional reference.
 *
 * @param s - the sprite to set the position of.
 * @param al - (bit mask) the position reference.
 */
void Level::alignSprite2World (Sprite2D &s, Level::alignment al)
{
    glm::vec3 pos = s.getPosition().get ();
    glm::vec3 size = s.getSize().getScaled ();

    if ((al & Level::CENTER) == Level::CENTER)
    {
        pos.x = (this->ui_lvl_width - size.x)/2;
        pos.y = (this->ui_lvl_height - size.y)/2;
    }//end if (al & Level::CENTER)

    if ((al & Level::TOP) == Level::TOP)
        pos.y = 0;
    else if ((al & Level::BOTTOM) == Level::BOTTOM)
        pos.y = this->ui_lvl_height - size.y;

    if ((al & Level::LEFT) == Level::LEFT)
        pos.x = 0;
    else if ((al & Level::RIGHT) == Level::RIGHT)
        pos.x = this->ui_lvl_width - size.x;

    s.getPosition().set (pos.x, pos.y);
}//end alignSprite ()

/**
 * @brief Set the sprite position relative to the screen.
 *
 * @param s - the sprite to set the position of.
 * @param al - (bit mask) the position reference.
 */
void Level::alignSprite2Camera (Sprite2D &s, Level::alignment al)
{
    glm::vec3 pos = s.getPosition().get ();
    glm::vec3 size = s.getSize().getScaled ();
    const Camera2D::frame_limits &frame =  this->p_view_window->getVisibleWorldLimits ();

    /* if ((al & Level::CENTER) == Level::CENTER) */
    {
        pos.x = ((frame.f_x2 - frame.f_x1) - size.x)/2;
        pos.y = ((frame.f_y2 - frame.f_y1) - size.y)/2;
    }//end if (al & Level::CENTER)

    if ((al & Level::TOP) == Level::TOP)
        pos.y = frame.f_y1;
    else if ((al & Level::BOTTOM) == Level::BOTTOM)
        pos.y = frame.f_y2 - size.y;

    if ((al & Level::LEFT) == Level::LEFT)
        pos.x = frame.f_x1;
    else if ((al & Level::RIGHT) == Level::RIGHT)
        pos.x = frame.f_x2 - size.x;

    s.getPosition().set (pos.x, pos.y, pos.z);
}//end alignSprite ()

